const shell = require('shelljs');
const path = require('path');
const fs = require('fs');
const {
  getLocales,
  getLocalePath,
  getLocaleSourceCatalogFiles,
  getLocaleCatalogPath,
  getLocaleCompiledCatalogPath,
  getLocaleMetadataPath,
  getLocaleName,
  getLocaleNativeName,
} = require('./lib/Locales');
const isWin = /^win/.test(process.platform);

const newIdeAppPath = path.join(__dirname, '..');

// Identify where msgcat is on the system
let msgcat = '';
if (isWin) {
  shell.echo(`ℹ️ Skipping translations compilation on Windows.`);
  shell.echo(
    `ℹ️ Pull Requests are welcome to add support for "msgcat" on Windows!`
  );
  shell.exit(0);
  return;
} else {
  msgcat = shell.exec('type msgcat 2>/dev/null', { silent: true }).stdout;
  if (!msgcat) {
    msgcat = shell.exec('find /usr -name "msgcat" -print -quit 2>/dev/null', {
      silent: true,
    }).stdout;
  }
}

msgcat = msgcat.trim();
if (!msgcat) {
  shell.echo(
    `ℹ️ msgcat not found not found on your computer - skipping translations compilation.`
  );
  shell.echo(
    `ℹ️ Install "gettext" with "brew install gettext" (macOS) or your Linux package manager.`
  );
  shell.exit(0);
  return;
}

const computeTranslationRatio = compiledCatalog => {
  const allMessages = Object.keys(compiledCatalog.messages);
  const sameAsKeyCount = allMessages.filter(
    key => compiledCatalog.messages[key] === key
  ).length;

  return 1 - sameAsKeyCount / allMessages.length;
};

const writeLocaleMetadata = object => {
  return new Promise((resolve, reject) => {
    const content = [
      `// This file is generated by compile-translations.js script`,
      `// prettier-ignore`,
      `module.exports = ${JSON.stringify(object, null, 2)};`,
      ``,
    ].join('\n');
    fs.writeFile(getLocaleMetadataPath(), content, err => {
      if (err) return reject(err);

      resolve();
    });
  });
};

const sanitizeMessagePo = path => {
  return new Promise((resolve, reject) => {
    fs.readFile(path, 'utf8', function(err, content) {
      if (err) {
        return reject(err);
      }
      let forbiddenStringsFound = [];
      let forbiddenStrings = [
        {
          str: 'n\\\\',
          regex: /n\\\\/g,
        },
        {
          str: '\\\\',
          regex: /\\\\/g,
        },
        {
          str: '\\ n',
          regex: /\\ n/g,
        },
        {
          str: '\\ t',
          regex: /\\ t/g,
        },
        {
          str: '\\ ',
          regex: /\\ /g,
        },
      ];
      let sanitizedContent = content;
      forbiddenStrings.forEach(forbiddenString => {
        if (sanitizedContent.search(forbiddenString.regex) !== -1) {
          forbiddenStringsFound.push(forbiddenString);
          sanitizedContent = sanitizedContent.replace(
            forbiddenString.regex,
            ' '
          );
        }
      });

      fs.writeFile(path, sanitizedContent, 'utf8', function(err) {
        if (err) {
          return reject(err);
        }

        resolve({
          forbiddenStringsFound,
        });
      });
    });
  });
};

getLocales()
  .then(
    locales =>
      Promise.all(
        locales.map(locale => {
          return new Promise(resolve => {
            // Concatenate all message catalogs into a single one for lingui-js.
            const files = getLocaleSourceCatalogFiles(locale);

            if (files.length === 1) {
              // For languages with a single source ("en", "pseudo_LOCALE"),
              // don't concatenate anything.
              const cpResult = shell.cp(
                path.join(getLocalePath(locale), files[0]),
                path.join(getLocalePath(locale), 'messages.po')
              );

              return resolve({
                locale,
                shellOutput: {
                  code: cpResult.code,
                  stdout: cpResult.stdout,
                  stderr: cpResult.stderr,
                },
              });
            }

            // Run msgcat. Use --no-wrap to allow to sanitize the catalog with
            // regex/string replace.
            // Use --use-first to avoid merging multiple translations for the same
            // string.
            shell.exec(
              msgcat + ` --no-wrap --use-first ${files.join(' ')} -o messages.po`,
              {
                cwd: getLocalePath(locale),
                silent: true,
              },
              (code, stdout, stderr) =>
                resolve({
                  locale,
                  shellOutput: {
                    code,
                    stdout,
                    stderr,
                  },
                })
            );
          });
        })
      ),
    error => {
      shell.echo(
        `❌ Error(s) occurred while listing locales folders: ` + error
      );
      shell.exit(1);
      return;
    }
  )
  .then(results => {
    //Display success and errors while concatenating translation catalogs for each locale.
    const successes = results.filter(
      ({ shellOutput }) => shellOutput.code === 0
    );
    const failures = results.filter(
      ({ shellOutput }) => shellOutput.code !== 0
    );

    const successesLocales = successes.map(({ locale }) => locale).join(',');
    if (successesLocales) {
      shell.echo(`ℹ️ Concatened translations for ${successesLocales}.`);
    }
    if (failures.length) {
      failures.forEach(({ locale, shellOutput }) => {
        shell.echo(
          `❌ Error(s) occurred while concatening translations for ${locale}: ` +
            shellOutput.stderr
        );
      });
    }

    return successes.map(({ locale }) => locale);
  })
  .then(locales => {
    // "Sanitize" all catalogs by removing and warning about bad characters
    // in translations that would break js-lingui (incorrect ICU message format).
    return Promise.all(
      locales.map(locale =>
        sanitizeMessagePo(getLocaleCatalogPath(locale)).then(results => {
          if (results.forbiddenStringsFound.length) {
            shell.echo(
              `⚠️ Found forbidden strings for locale ${locale} (replaced by spaces):`
            );
            results.forbiddenStringsFound.forEach(({ str }) => {
              shell.echo(`  * Found ${str}`);
            });
          }
        })
      )
    ).then(() => locales);
  })
  .then(locales => {
    // Launch "lingui compile" for transforming .PO files into
    // js files ready to be used with @lingui/react newIDE translations
    shell.exec('node node_modules/.bin/lingui compile', {
      cwd: newIdeAppPath,
    });

    return locales;
  })
  .then(locales => {
    // Compute some stats about the languages...
    return Promise.all(
      locales.map(locale => {
        const compiledCatalog = require(getLocaleCompiledCatalogPath(locale));

        return {
          languageCode: locale,
          languageName: getLocaleName(locale),
          languageNativeName: getLocaleNativeName(locale),
          translationRatio: computeTranslationRatio(compiledCatalog),
        };
      })
    );
  })
  .then(
    // ... and store the stats in LocaleMetadata.js, to be displayed/used
    // in the editor.
    localesMetadata => writeLocaleMetadata(localesMetadata),
    error => {
      shell.echo(
        `❌ Error(s) occurred while computing ${getLocaleMetadataPath()}: ` +
          error
      );
      shell.exit(1);
      return;
    }
  )
  .then(
    () => {
      shell.echo(
        `✅ Translations compiled and metadata written in ${getLocaleMetadataPath()}`
      );
      shell.exit(0);
      return;
    },
    error => {
      shell.echo(
        `❌ Error(s) occurred while writing ${getLocaleMetadataPath()}: ` +
          error
      );
      shell.exit(1);
      return;
    }
  );
